﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using WickedSick.ClientComponents.Music.SDBService;

namespace WickedSick.ClientComponents.Music
{
    public delegate void CommunicationCompleteDelegate(object sender, int CommunicationType);
    public delegate void UserAuthenticatedCompleteDelegate(object sender, Guid id);
    public delegate void UserLogoffCompleteDelegate(object sender);

    /// <summary>
    /// This class is used to handle business intelligence for service calls and updates to the media bank.
    /// Media bank is maintained in a file to reduce service calls.
    /// </summary>
    /// <example>
    /// MediaBI _mbi = new MediaBI("MediaBank.sdb");
    /// _mbi.InitializeMedia(UserID);
    /// _mbi.InitializePlaylists(UserID);
    /// </example>
    public class MediaBI
    {
        private SDBServiceClient ssc;

        private MediaBank mb;
        private string mb_filepath;
        
        private List<Artist> _TempArtists;
        private int _ArtistChunkTally;
        private Dictionary<int, Song> _TempSongs;
        private int _SongChunkTally;
        private Dictionary<int, Album> _TempAlbums;
        private int _AlbumChunkTally;
        private List<int> _TempLibrary;
        private int done = 0;

        private int _AuditChunkTally;
        private List<Audit> _TempAudits;

        private int _PlaylistChunkTally;
        private List<Playlist> _TempPlaylists;
        
        /// <summary>
        /// This event is called upon many cases when service calls have been completed.
        /// </summary>
        /// <remarks>
        /// For communication types: (1 = Media loaded, 2 = Media updated, 3 = Playlists loaded)
        /// </remarks>
        public event CommunicationCompleteDelegate CommunicationComplete;
        /// <summary>
        /// This event is called after querying sdb for authentication.
        /// </summary>
        /// <remarks>
        /// The id will be Guid.Empty if not authenticated.
        /// </remarks>
        public event UserAuthenticatedCompleteDelegate UserAuthenticatedComplete;
        /// <summary>
        /// This event is called after the WCF service has disposed of a logged in user's resources.
        /// </summary>
        public event UserLogoffCompleteDelegate UserLogoffComplete;

        /// <summary>
        /// Constructor used to build Media Business Intelligence.
        /// </summary>
        /// <remarks>
        /// The filepath given will attempt to load all media (Artists/Albums/Songs/Playlists).
        /// Also prepares service calls to server.
        /// </remarks>
        /// <param name="media_bank_filepath">media_bank_filepath is a path to the file which saves all media data.</param>
        public MediaBI(string media_bank_filepath)
        {
            mb_filepath = media_bank_filepath;
            if (File.Exists(media_bank_filepath))
            {
                Stream st = File.Open(media_bank_filepath, FileMode.Open);
                BinaryFormatter bf = new BinaryFormatter();
                try
                {
                    mb = (MediaBank)bf.Deserialize(st);
                }
                catch (SerializationException)
                {
                    mb = new MediaBank();
                }
                st.Close();
            }
            else
            {
                mb = new MediaBank();
            }

            mb.AssociateOwnerObjects();
            mb.BuildAllSongs();

            ssc = new SDBServiceClient("BasicHttpBinding_ISDBService");
            ssc.LogoffMemberCompleted += new EventHandler<LogoffMemberCompletedEventArgs>(ssc_LogoffMemberCompleted);
            ssc.AuthenticateMemberCompleted += new EventHandler<AuthenticateMemberCompletedEventArgs>(ssc_AuthenticateMemberCompleted);
            ssc.RequestAllAlbumsCompleted += new EventHandler<RequestAllAlbumsCompletedEventArgs>(ssc_RequestAllAlbumsCompleted);
            ssc.RequestNextAlbumChunkCompleted += new EventHandler<RequestNextAlbumChunkCompletedEventArgs>(ssc_RequestNextAlbumChunkCompleted);
            ssc.RequestAllArtistsCompleted += new EventHandler<RequestAllArtistsCompletedEventArgs>(ssc_RequestAllArtistsCompleted);
            ssc.RequestNextArtistChunkCompleted += new EventHandler<RequestNextArtistChunkCompletedEventArgs>(ssc_RequestNextArtistChunkCompleted);
            ssc.RequestAllSongsCompleted += new EventHandler<RequestAllSongsCompletedEventArgs>(ssc_RequestAllSongsCompleted);
            ssc.RequestNextSongChunkCompleted += new EventHandler<RequestNextSongChunkCompletedEventArgs>(ssc_RequestNextSongChunkCompleted);
            ssc.RequestLibraryIDsCompleted += new EventHandler<RequestLibraryIDsCompletedEventArgs>(ssc_RequestLibraryIDsCompleted);
            ssc.RequestChangesCompleted += new EventHandler<RequestChangesCompletedEventArgs>(ssc_RequestChangesCompleted);
            ssc.RequestNextChangesChunkCompleted += new EventHandler<RequestNextChangesChunkCompletedEventArgs>(ssc_RequestNextChangesChunkCompleted);
            ssc.RequestAllPlaylistsCompleted += new EventHandler<RequestAllPlaylistsCompletedEventArgs>(ssc_RequestAllPlaylistsCompleted);
            ssc.RequestNextPlaylistChunkCompleted += new EventHandler<RequestNextPlaylistChunkCompletedEventArgs>(ssc_RequestNextPlaylistChunkCompleted);
        }

        #region MEDIA INIT/SAVE

        /// <summary>
        /// Acquires all media (Artists/Albums/Songs) from server database.
        /// </summary>
        /// <remarks>
        /// Also loads Song.SongLocation given the inputted MemberID.
        /// </remarks>
        /// <param name="MemberID">MemberID is used to determine SongLocation.</param>
        public void InitializeMedia(int MemberID)
        {
            if (mb.Artists == null || mb.Artists.Count < 1)
            {
                done = 0;
                _ArtistChunkTally = 0;
                _TempArtists = new List<Artist>();
                ssc.RequestAllArtistsAsync();
                
                _AlbumChunkTally = 0;
                _TempAlbums = new Dictionary<int, Album>();
                ssc.RequestAllAlbumsAsync();

                _SongChunkTally = 0;
                _TempSongs = new Dictionary<int, Song>();
                ssc.RequestAllSongsAsync();
                
                _TempLibrary = new List<int>();
                ssc.RequestLibraryIDsAsync(MemberID);
            }
        }

        /// <summary>
        /// Acquires any changes to the server database since "LastUpdate".
        /// </summary>
        /// <param name="LastUpdate">LastUpdate is the DateTime that local client was updated.</param>
        public void ApplyRecentMediaChanges(DateTime LastUpdate)
        {
            _TempAudits = new List<Audit>();
            _AuditChunkTally = 0;
            ssc.RequestChangesAsync(LastUpdate);
        }

        /// <summary>
        /// Acquires a member's playlists from the server database.
        /// </summary>
        /// <remarks>InitializeMedia() or new MediaBI(string) must be called first to ensure Songs are loaded.</remarks>
        /// <param name="MemberID">MemberID is used to grab only this member's playlists.</param>
        public void InitializePlaylists(int MemberID)
        {
            if (mb.Playlists == null || mb.Playlists.Count < 1)
            {
                _PlaylistChunkTally = 0;
                _TempPlaylists = new List<Playlist>();
                ssc.RequestAllPlaylistsAsync(MemberID);
            }
        }
        
        /// <summary>
        /// Saves the media bank to file.
        /// </summary>
        /// <remarks>The file saved will be determined upon the MediaBI's constructor input parameter "media_bank_filepath".</remarks>
        public void SendMediaToFile()
        {
            Stream st = File.Open(mb_filepath, FileMode.Create);
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(st, mb);
            st.Close();
        }

        /// <summary>
        /// Authenticates username and password through WCF service.
        /// </summary>
        /// <param name="user"></param>
        /// <param name="password"></param>
        public void AuthenticateUser(string user, string password)
        {
            ssc.AuthenticateMemberAsync(user, password);
        }

        /// <summary>
        /// Logs user off of system.
        /// </summary>
        /// <param name="id">User guid tied to wcf service.</param>
        public void LogoffUser(Guid id)
        {
            ssc.LogoffMemberAsync(id);
        }

        private void FinishMediaLoad()
        {
            if (done == 15)
            {
                mb.LoadFullBank(ref _TempArtists, ref _TempAlbums, ref _TempSongs, ref _TempLibrary);
                CallCommunicationComplete(1);
            }
        }

        private void FinishMediaUpdate()
        {
            mb.ApplyBankUpdates(ref _TempAudits);
            CallCommunicationComplete(2);
        }

        private void FinishPlaylistLoad()
        {
            mb.LoadPlaylists(ref _TempPlaylists);
            CallCommunicationComplete(3);
        }

        #endregion

        #region WCF EVENTS

        private void ssc_AuthenticateMemberCompleted(object sender, AuthenticateMemberCompletedEventArgs e)
        {
            CallUserAuthenticatedComplete((Guid)e.Result);
        }
        
        private void ssc_LogoffMemberCompleted(object sender, LogoffMemberCompletedEventArgs e)
        {
            CallUserLogoffComplete();
        }

        private void ssc_RequestAllArtistsCompleted(object sender, RequestAllArtistsCompletedEventArgs e)
        {
            ChunkDataOfArtistDataTepVWBla temp = (ChunkDataOfArtistDataTepVWBla)e.Result;
            for (int i = (temp.ChunkNumber + 1); i < temp.TotalChunks; i++)
            {
                ssc.RequestNextArtistChunkAsync(i);
            }
            HandleArtistChunks(temp);
        }

        private void ssc_RequestNextArtistChunkCompleted(object sender, RequestNextArtistChunkCompletedEventArgs e)
        {
            HandleArtistChunks((ChunkDataOfArtistDataTepVWBla)e.Result);
        }

        private void HandleArtistChunks(ChunkDataOfArtistDataTepVWBla curChunk)
        {
            foreach (int k in curChunk.Chunk.Keys)
            {
                ArtistData ad = curChunk.Chunk[k];
                Artist a = new Artist();
                a.ID = k;
                a.Name = ad.Name;
                foreach (int aid in ad.AlbumIDs)
                {
                    Album al = new Album();
                    al.ID = aid;
                    a.Albums.Add(aid, al);
                }
                _TempArtists.Add(a);
            }
            _ArtistChunkTally++;

            if (_ArtistChunkTally >= curChunk.TotalChunks)
            {
                done += 2;
                FinishMediaLoad();
            }
        }

        private void ssc_RequestAllAlbumsCompleted(object sender, RequestAllAlbumsCompletedEventArgs e)
        {
            ChunkDataOfAlbumDataTepVWBla temp = (ChunkDataOfAlbumDataTepVWBla)e.Result;
            for (int i = (temp.ChunkNumber + 1); i < temp.TotalChunks; i++)
            {
                ssc.RequestNextAlbumChunkAsync(i);
            }
            HandleAlbumChunks(temp);
        }

        private void ssc_RequestNextAlbumChunkCompleted(object sender, RequestNextAlbumChunkCompletedEventArgs e)
        {
            HandleAlbumChunks((ChunkDataOfAlbumDataTepVWBla)e.Result);
        }

        private void HandleAlbumChunks(ChunkDataOfAlbumDataTepVWBla curChunk)
        {
            foreach (int k in curChunk.Chunk.Keys)
            {
                AlbumData ad = curChunk.Chunk[k];
                Album a = new Album();
                a.ID = k;
                a.Name = ad.Name;
                a.ReleaseDate = new DateTime(ad.ReleaseDate);
                foreach (int sid in ad.SongIDs)
                {
                    Song s = new Song();
                    s.ID = sid;
                    a.Songs.Add(sid, s);
                }
                _TempAlbums.Add(k, a);
            }
            _AlbumChunkTally++;

            if (_AlbumChunkTally >= curChunk.TotalChunks)
            {
                done += 4;
                FinishMediaLoad();
            }
        }

        private void ssc_RequestAllSongsCompleted(object sender, RequestAllSongsCompletedEventArgs e)
        {
            ChunkDataOfSongDataTepVWBla temp = (ChunkDataOfSongDataTepVWBla)e.Result;
            for (int i = (temp.ChunkNumber + 1); i < temp.TotalChunks; i++)
            {
                ssc.RequestNextSongChunkAsync(i);
            }
            HandleSongChunks(temp);
        }

        private void ssc_RequestNextSongChunkCompleted(object sender, RequestNextSongChunkCompletedEventArgs e)
        {
            HandleSongChunks((ChunkDataOfSongDataTepVWBla)e.Result);
        }

        private void HandleSongChunks(ChunkDataOfSongDataTepVWBla curChunk)
        {
            foreach (int k in curChunk.Chunk.Keys)
            {
                SongData sd = curChunk.Chunk[k];
                Song s = new Song();
                s.Duration = TimeSpan.FromTicks(sd.Duration);
                s.ID = k;
                s.SongLocation = (SongLocations)sd.Location;
                s.Title = sd.Title;
                s.TrackNumber = sd.TrackNumber;
                s.UploadDate = new DateTime(sd.UploadDate);
                _TempSongs.Add(k, s);
            }
            _SongChunkTally++;

            if (_SongChunkTally >= curChunk.TotalChunks)
            {
                done += 1;
                FinishMediaLoad();
            }
        }
        
        private void ssc_RequestLibraryIDsCompleted(object sender, RequestLibraryIDsCompletedEventArgs e)
        {
            _TempLibrary = (List<int>)e.Result;
            done += 8;
            FinishMediaLoad();
        }

        private void ssc_RequestChangesCompleted(object sender, RequestChangesCompletedEventArgs e)
        {
            ChunkDataOfAuditDataTepVWBla temp = (ChunkDataOfAuditDataTepVWBla)e.Result;
            for (int i = (temp.ChunkNumber + 1); i < temp.TotalChunks; i++)
            {
                ssc.RequestNextChangesChunkAsync(temp.ID, i);
            }
            HandleAuditChunks(temp);
        }

        private void ssc_RequestNextChangesChunkCompleted(object sender, RequestNextChangesChunkCompletedEventArgs e)
        {
            HandleAuditChunks((ChunkDataOfAuditDataTepVWBla)e.Result);
        }

        private void HandleAuditChunks(ChunkDataOfAuditDataTepVWBla curChunk)
        {
            foreach (int k in curChunk.Chunk.Keys)
            {
                AuditData ad = curChunk.Chunk[k];
                Audit a = new Audit();
                a.AuditType = ad.AuditType;
                a.FieldName = ad.FieldName;
                a.NewValue = ad.NewValue;
                a.ObjectID = ad.ObjectID;
                a.ObjectType = ad.ObjectType;
                a.AuditDate = new DateTime(ad.AuditDate);
                _TempAudits.Add(a);
            }

            _AuditChunkTally++;
            if (_AuditChunkTally >= curChunk.TotalChunks)
            {
                FinishMediaUpdate();
            }
        }

        private void ssc_RequestAllPlaylistsCompleted(object sender, RequestAllPlaylistsCompletedEventArgs e)
        {
            ChunkDataOfPlaylistDataTepVWBla temp = (ChunkDataOfPlaylistDataTepVWBla)e.Result;
            for (int i = (temp.ChunkNumber + 1); i < temp.TotalChunks; i++)
            {
                ssc.RequestNextPlaylistChunkAsync(temp.ID, i);
            }
            HandlePlaylistChunks(temp);
        }

        private void ssc_RequestNextPlaylistChunkCompleted(object sender, RequestNextPlaylistChunkCompletedEventArgs e)
        {
            HandlePlaylistChunks((ChunkDataOfPlaylistDataTepVWBla)e.Result);
        }

        private void HandlePlaylistChunks(ChunkDataOfPlaylistDataTepVWBla curChunk)
        {
            foreach (int k in curChunk.Chunk.Keys)
            {
                PlaylistData pd = curChunk.Chunk[k];
                Playlist p = new Playlist();
                p.ID = pd.PlaylistID;
                p.Name = pd.Name;
                p.Description = pd.Description;
                foreach (int i in pd.SongIDs)
                {
                    Song s = new Song();
                    s.ID = i;
                    p.Songs.Add(s);
                }
                _TempPlaylists.Add(p);
            }

            _PlaylistChunkTally++;
            if (_PlaylistChunkTally >= curChunk.TotalChunks)
            {
                FinishPlaylistLoad();
            }
        }

        #endregion

        private void CallCommunicationComplete(int CommunicationType)
        {
            CommunicationCompleteDelegate obj = this.CommunicationComplete;
            if (obj != null)
            {
                obj(this, CommunicationType);
            }
        }

        private void CallUserAuthenticatedComplete(Guid id)
        {
            UserAuthenticatedCompleteDelegate obj = this.UserAuthenticatedComplete;
            if (obj != null)
            {
                obj(this, id);
            }
        }
        
        private void CallUserLogoffComplete()
        {
            UserLogoffCompleteDelegate obj = this.UserLogoffComplete;
            if (obj != null)
            {
                obj(this);
            }
        }

        /// <summary>
        /// This is the media bank which holds all Media (Artists/Albums/Songs/Playlists)
        /// </summary>
        public MediaBank Bank
        {
            get { return mb; }
        }
    }
}
